﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Threading;
using Pfz.Collections;

namespace Pfz.Extensions
{
	/// <summary>
	/// Adds the SetValueAsyncAndOnce method to DependencyObjects.
	/// </summary>
	public static class PfzDependencyObjectExtensions
	{
		private sealed class InternalAutoTrimDictionary:
			AutoTrimDictionary<KeyValuePair<DependencyObject, DependencyProperty>, object>
		{
			internal new object DisposeLock
			{	
				get
				{
					return base.DisposeLock;
				}
			}
		}

		private static readonly InternalAutoTrimDictionary _valuesToSet = new InternalAutoTrimDictionary();

		/// <summary>
		/// Calls Dispatcher.BeginIncoke to set a value to a property.
		/// If many calls are done before the begin invoke is executed, only the last Set remains, all others
		/// will simple be ignored, effectivelly doing only one set.
		/// </summary>
		public static void SetValueAsyncAndOnce(this DependencyObject dependencyObject, DependencyProperty dependencyProperty, object value, DispatcherPriority priority = DispatcherPriority.Normal)
		{
			if (dependencyObject == null)
				throw new ArgumentNullException("dependencyObject");

			if (dependencyProperty == null)
				throw new ArgumentNullException("dependencyProperty");

			var propertyType = dependencyProperty.PropertyType;
			if (value == null)
			{
				if (propertyType.IsValueType)
					throw new ArgumentException("You are trying to set null to a " + dependencyProperty.Name + " which is a value-type property.");
			}
			else
			{
				Type valueType = value.GetType();
				if (!propertyType.IsAssignableFrom(valueType))
					throw new ArgumentException("value of type " + valueType.FullName + " can't be assigned to " + dependencyProperty.Name + ", which is of type " + propertyType.FullName + ".");
			}

			var pair = new KeyValuePair<DependencyObject, DependencyProperty>(dependencyObject, dependencyProperty);
			lock(_valuesToSet.DisposeLock)
			{
				var valuesToSet = _valuesToSet;
				if (valuesToSet.ContainsKey(pair))
					valuesToSet[pair] = value;
				else
				{
					valuesToSet.Add(pair, value);

					dependencyObject.Dispatcher.BeginInvoke
					(
						new Action<KeyValuePair<DependencyObject, DependencyProperty>>(_SetValueAsync),
						priority,
						pair
					);
				}
			}
		}
		private static void _SetValueAsync(KeyValuePair<DependencyObject, DependencyProperty> pair)
		{
			var dependencyObject = pair.Key;
			var dependencyProperty = pair.Value;
			object value;
			lock(_valuesToSet.DisposeLock)
			{
				var valuesToSet = _valuesToSet;
				value = valuesToSet[pair];
				valuesToSet.Remove(pair);
			}

			dependencyObject.SetValue(dependencyProperty, value);
		}
	}
}
